home *** CD-ROM | disk | FTP | other *** search
- //
- // MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
- // Originally written by Drew Davidson.
- // Copyright (c) 1994 by Drew Davidson.
- // Modified by Don Yacktman for inclusion into the MiscKit.
- // Fixed up by Carl Lindberg, Don Yacktman, and Steve Hayman.
- // Version 1.3. All rights reserved.
- // This notice may not be removed from this source code.
- //
- // This object is included in the MiscKit by permission from the author
- // and its use is governed by the MiscKit license, found in the file
- // "LICENSE.rtf" in the MiscKit distribution. Please refer to that file
- // for a list of all applicable permissions and restrictions.
- //
-
- /*----------------------------------------------------------------------------
- $Source$
-
- SYNOPSIS
- Handles a subprocess that is fork()'ed and handles delegate
- notification of events that occur such as output and errors.
-
- From Subprocess example by Charles L. Oei
- pty support (removed) by Joe Freeman
- with encouragement from Kristofer Younger
- Subprocess Example, Release 2.0
- NeXT Computer, Inc.
-
- Modified to support signals SIGSTOP and SIGCONT, and to wait for error
- code from terminated process, and to add delegate methods for the output by
- Drew Davidson
-
- Modified for MiscKit inclusion by Don Yacktman
- Debugged by Carl Lindberg, Don Yacktman, Steve Hayman
- PTY support added back in by Carl Lindberg
- Synchronous operation by Steve Hayman
-
- REVISIONS
- $Log$
- ----------------------------------------------------------------------------*/
- #import <libc.h>
- #import <misckit/MiscString.h>
- #import <misckit/MiscStringArray.h>
- #import <misckit/MiscSubprocess.h>
-
- extern int wait4(int, union wait *, int, struct rusage *);
- static void stdoutFdHandler(int theFd, id self);
- static void stderrFdHandler(int theFd, id self);
-
- #define PIPE_ERROR "Error starting UNIX pipes to subprocess."
- #define VFORK_ERROR "Error starting UNIX vfork of subprocess."
- #define EXEC_ERROR "Error starting UNIX exec of subprocess."
- #define PTY_TEMPLATE "/dev/pty??"
- #define PTY_LENGTH 11
-
- @interface MiscSubprocess(private)
-
- - _childDidExit;
- - _stdoutBuffer;
- - _stderrBuffer;
- - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
- - _setRunning:(BOOL)yn;
-
- @end
-
- static void
- getptys (int *master, int *slave)
- // attempt to setup the ptys
- {
- char device[PTY_LENGTH];
- char *block, *num;
- char *blockLoc; // specifies the location of block for the device string
- char *numLoc; // specifies the pty name with the digit ptyxD
- char *msLoc; // specifies the master (ptyxx) or slave (ttyxx)
-
- struct sgttyb setp =
- {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
- struct tchars setc =
- {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
- struct ltchars sltc =
- {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
- int lset =
- (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
- int setd = NTTYDISC;
-
- strcpy(device, PTY_TEMPLATE); // string constants are not writable
- blockLoc = &device[ strlen("/dev/pty") ];
- numLoc = &device[ strlen("/dev/pty?") ];
- msLoc = &device[ strlen("/dev/") ];
- for (block = "pqrs"; *block; block++)
- {
- *blockLoc = *block;
- for (num = "0123456789abcdef"; *num; num++)
- {
- *numLoc = *num;
- *master = open(device, O_RDWR);
- if (*master >= 0)
- {
- *msLoc = 't';
- *slave = open(device, O_RDWR);
- if (*slave >= 0)
- {
- (void) ioctl(*slave, TIOCSETP, (char *)&setp);
- (void) ioctl(*slave, TIOCSETC, (char *)&setc);
- (void) ioctl(*slave, TIOCSETD, (char *)&setd);
- (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
- (void) ioctl(*slave, TIOCLSET, (char *)&lset);
- return;
- }
- // Change it back if the open of the "slave" of the pty fails.
- *msLoc = 'p'; // to cause it to open the master end of pty
- // the next time through the loop.
- }
- } /* hunting through a bank of ptys */
- } /* hunting through blocks of ptys in all the right places */
- *master = -1;
- *slave = -1;
- }
-
-
- @implementation MiscSubprocess
-
- /*----------------------------< PRIVATE METHODS >----------------------------*/
- /*
- * cleanup after a child process exits
- */
- - _childDidExit
- {
- union wait w; int status = 0;
- MiscSubprocessEndCode code = Misc_UnknownEndCode;
-
- if (wait4(childPid,&w,WUNTRACED,NULL) > 0) {
- IMP done;
- if (WIFEXITED(w)) {
- code = Misc_Exited;
- status = w.w_retcode;
- } else {
- if (WIFSTOPPED(w)) {
- code = Misc_Stopped;
- status = w.w_stopsig;
- } else {
- if (WIFSIGNALED(w)) {
- code = Misc_Signaled;
- status = w.w_termsig;
- }
- }
- }
-
- if ( dpsStdoutFromChild >= 0 )
- DPSRemoveFD(dpsStdoutFromChild);
- if ( dpsStderrFromChild >= 0 )
- DPSRemoveFD(dpsStderrFromChild);
-
- fclose(fpFromChild);
- close(stdoutFromChild);
- close(stderrFromChild);
- fclose(fpToChild);
- running = NO;
- done = [delegate methodFor:@selector(subprocess:done::)];
- done(delegate,@selector(subprocess:done::),self,status,code);
- }
- return self;
- }
-
- - _stdoutBuffer
- {
- return stdoutBuffer;
- }
-
- - _stderrBuffer
- {
- return stderrBuffer;
- }
-
- /*
- * DPS handler for output from subprocess
- */
-
- - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString
- { // re-written by Carl Lindberg to make it safer.
- int bufferCount, currleft = BUFSIZ / 2;
- do {
- int currentLength = [aString length];
- char buf[(currleft * 2) + 1];
-
- currleft *=2;
- bufferCount = read(theFd, buf, currleft);
- if (bufferCount <= 0) {
- if (currentLength > 0)
- [self flushBuffer:aString];
-
- /*
- * DPSAddFd() just happens to invoke the handler
- * function even though there may be no data to
- * read on this fd. So don't blindly exit the
- * first time read returns 0; wait until
- * both stderr and stdout
- * have closed before calling _childDidExit; otherwise
- * you risk losing data if stderr happens to be
- * done first.
- */
- if ( theFd == stdoutFromChild )
- stdoutIsDone = YES;
- else if ( theFd == stderrFromChild )
- stderrIsDone = YES;
-
- if ( stdoutIsDone && stderrIsDone )
- [self _childDidExit];
- return self;
- }
- buf[bufferCount] = 0;
- [aString cat:buf];
- } while (bufferCount == currleft);
- [self flushBuffer:aString];
- return self;
- }
-
- /*
- * DPS handler for output from subprocess
- */
- static void stdoutFdHandler(int theFd,id self)
- {
- [self _fdHandler:theFd method:@selector(subprocess:output:)
- buffer:[self _stdoutBuffer]];
- }
-
- static void stderrFdHandler(int theFd,id self)
- {
- [self _fdHandler:theFd method:@selector(subprocess:stderrOutput:)
- buffer:[self _stderrBuffer]];
- }
-
- - _setRunning:(BOOL)yn
- {
- running = yn;
- return self;
- }
-
- /*---------------------------< INIT/FREE METHODS >---------------------------*/
-
- - init:(const char *)aString withDelegate:theDelegate
- keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
- asynchronously:(BOOL)async
- {
- [super init];
- usePtys = ptyFlag;
- asynchronous = async;
- stdoutBuffer = [[MiscString allocFromZone:[self zone]]
- initCapacity:BUFSIZ];
- stderrBuffer = [[MiscString allocFromZone:[self zone]]
- initCapacity:BUFSIZ];
- environment = [[MiscStringArray allocFromZone:[self zone]] init];
- dpsStdoutFromChild = dpsStderrFromChild = -1; // DPSAddFd() not in use yet
- if (flag) {
- int i;
- for (i=0; environ[i]; i++)
- [environment addString:environ[i]];
- }
- [self setDelegate:theDelegate];
- [self setExecArgs:"/bin/sh" :"sh" :"-c"];
- if (aString)
- [self execute:aString withPtys:usePtys asynchronously:asynchronous];
- return self;
- }
-
- - free
- {
- free(execArgs[0]);
- free(execArgs[1]);
- free(execArgs[2]);
- [self terminate:self];
- [self _childDidExit]; // does DPSRemoveFd for us
- [stdoutBuffer free];
- [stderrBuffer free];
- [environment free];
- return [super free];
- }
-
- /*-----------------------------< OTHER METHODS >-----------------------------*/
-
- - execute:(const char *)aString withPtys:(BOOL)ptyFlag
- asynchronously:(BOOL)async
- {
- int pipeTo[2], pipeFrom[2], pipeStderr[2]; // for stderr to different fd
- int tty, numFds, fd, processGroup;
- char hail[BUFSIZ];
-
- if ([self isRunning]) return nil;
- if (ptyFlag)
- {
- tty = open("/dev/tty", O_RDWR);
- getptys(&masterPty,&slavePty);
- if (masterPty <= 0 || slavePty <= 0 || pipe(pipeStderr) < 0) {
- [delegate perform:@selector(subprocess:error:) with:self
- with:(void *)"Error grabbing ptys for subprocess."];
- return self;
- }
- // remove the controlling tty if launched from a shell,
- // but not Workspace;
- // so that we have job control over the parent application in shell
- // and so that subprocesses can be restarted in Workspace
- if ((tty<0) && ((tty = open("/dev/tty", 2))>=0)) {
- ioctl(tty, TIOCNOTTY, 0);
- close(tty);
- }
- }
- else {
- if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0) {
- [delegate perform:@selector(subprocess:error:)
- with:self with:(void *)PIPE_ERROR];
- return nil;
- }
- }
- switch (childPid = vfork()) {
- case -1: /* error */
- [delegate perform:@selector(subprocess:error:)
- with:self with:(void *)VFORK_ERROR];
- return self;
- break;
- case 0: /* child */
- if (ptyFlag) {
- dup2(slavePty, 0);
- dup2(slavePty, 1);
- dup2(pipeStderr[1], 2); //stderr
- }
- else {
- dup2(pipeTo[0], 0);
- dup2(pipeFrom[1], 1); /* get stdout from process */
- dup2(pipeStderr[1], 2); /* get stderr here */
- }
- numFds = getdtablesize();
- for (fd = 3; fd < numFds; fd++) close(fd);
- processGroup = getpid();
- ioctl(0, TIOCSPGRP, (char *)&processGroup);
- setpgrp(0, processGroup);
- fgets(hail, BUFSIZ, stdin); // wait for parent
- [self execChild:aString];
- [delegate perform:@selector(subprocess:error:)
- with:self with:(void *)EXEC_ERROR];
- [self error:"vfork (child) returned!"];
- break;
- default: /* parent */
- if (ptyFlag) {
- close(slavePty);
- close(pipeStderr[1]);
- fpToChild = fdopen(masterPty,"w");
- stdoutFromChild = masterPty;
- stderrFromChild = pipeStderr[0];
- }
- else {
- close(pipeTo[0]);
- close(pipeFrom[1]);
- close(pipeStderr[1]);
- fpToChild = fdopen(pipeTo[1], "w");
- stdoutFromChild = pipeFrom[0];
- fpFromChild = fdopen(pipeFrom[0],"r");
- stderrFromChild = pipeStderr[0];
- }
- // Set buffering method, also make it use its own buffers
- setbuf(fpToChild, NULL); /* no buffering */
- if (!ptyFlag) setbuf(fpFromChild, NULL);
- if ( async ) {
- // Don't read output now;
- // Tell DPS to notify us when data is ready.
- stdoutIsDone = NO;
- DPSAddFD(dpsStdoutFromChild = stdoutFromChild,
- (DPSFDProc)stdoutFdHandler, self,
- NX_MODALRESPTHRESHOLD + 1);
- stderrIsDone = NO;
- DPSAddFD(dpsStderrFromChild = stderrFromChild,
- (DPSFDProc)stderrFdHandler, self,
- NX_MODALRESPTHRESHOLD + 1);
- running = YES;
- fputs("Run away! Run away!\n", fpToChild); // tell child to go
- } else { //synchronous
- // from Steve Hayman for synch processes...
- // modified by Nicolas Droux and Carl Lindberg
- // Tell the subprocess to go right now, we'll read all its
- // output until end-of-file and return when the subprocess
- // has exited. We still fork, but we won't return to the
- // main thread until the child exits...
-
- fd_set readfds;
- int highestFdNum;
-
- running = YES;
- dpsStdoutFromChild = dpsStderrFromChild = -1;
- //Not using DPSAddFd()
-
- highestFdNum = stdoutFromChild > stderrFromChild ?
- stdoutFromChild : stderrFromChild;
-
- stdoutIsDone = stderrIsDone = NO;
- fputs("Run away! Run away!\n", fpToChild);
- do {
- // set up the set of fd's we are using
- FD_ZERO(&readfds);
- FD_SET(stdoutFromChild, &readfds);
- FD_SET(stderrFromChild, &readfds);
-
- // select the ones that nead reading from
- select(highestFdNum+1,&readfds,NULL,NULL,NULL);
-
- if (FD_ISSET(stdoutFromChild, &readfds))
- stdoutFdHandler(stdoutFromChild, self);
- if (FD_ISSET(stderrFromChild, &readfds))
- stderrFdHandler(stderrFromChild, self);
-
- } while(!stdoutIsDone || !stderrIsDone);
- // The above condition is the same one that _fdHandler uses to
- // call _childDidExit, so that call has already been done by this point.
- }
- break;
- }
- return self;
- }
-
- - setExecArgs:(const char *)a0 :(const char *)a1 :(const char *)a2
- {
- int i;
-
- for (i=0; i<3; i++) if (execArgs[i]) free(execArgs[i]);
- execArgs[0] = NXCopyStringBufferFromZone(a0, [self zone]);
- execArgs[1] = NXCopyStringBufferFromZone(a1, [self zone]);
- execArgs[2] = NXCopyStringBufferFromZone(a2, [self zone]);
- return self;
- }
-
- - execChild:(const char *)aString
- {
- /*
- * we exec a /bin/sh so that cmds are easier to specify for the user
- * Unlike the old method, we don't automatically
- * use "/bin/sh", "sh", "-c" anymore
- */
- execle( execArgs[0], execArgs[1], execArgs[2], aString, 0,
- [environment stringArray] );
- // old: execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]);
- return self; // shouldn't get here
- }
-
- - setDelegate:anObject
- {
- delegate = anObject;
- return self;
- }
-
- - delegate
- {
- return delegate;
- }
-
- - environment
- {
- return environment;
- }
-
- - (SEL)outputMethodForBuffer:aBuffer
- {
- SEL aSelector;
- if (aBuffer == [self _stdoutBuffer])
- aSelector = @selector(subprocess:output:);
- else {
- if (aBuffer == [self _stderrBuffer])
- aSelector = @selector(subprocess:stderrOutput:);
- else aSelector = NULL;
- }
- return aSelector;
- }
-
- - flushBuffer:aString as:aBuffer
- {
- if ([aString length] > 0) {
- SEL aSelector = [self outputMethodForBuffer:aBuffer];
- if (aSelector)
- [delegate perform:aSelector
- with:self with:(id)[aString stringValue]];
- [aString setStringValue:""];
- }
- return nil;
- }
-
- - flushBuffer:aString
- {
- return [self flushBuffer:aString as:aString];
- }
-
- - send:(const char *)string withNewline:(BOOL)wantNewline
- {
- fputs(string,fpToChild);
- if (wantNewline)
- fputc('\n',fpToChild);
- return self;
- }
-
- - send:(const char *)string
- {
- [self send:string withNewline:YES];
- return self;
- }
-
- /*
- * Returns the process id of the process (and therefore the process group
- * of the job)
- */
- - (int)pid
- {
- return childPid;
- }
-
- - pause:sender
- {
- if (!paused) {
- killpg(childPid,SIGSTOP); /* pause the process group */
- paused = YES;
- }
- return self;
- }
-
- - resume:sender
- {
- if (paused) {
- killpg(childPid,SIGCONT); /* resume the process group */
- paused = NO;
- }
- return self;
- }
-
- - (BOOL)isPaused
- {
- return paused;
- }
-
- - (BOOL)isRunning
- {
- return running;
- }
-
- - (BOOL)usePtys { return usePtys; }
- - setUsePtys:(BOOL)flag { usePtys = flag; return self; }
-
- - (BOOL)asynchronous { return asynchronous; }
- - setAsynchronous:(BOOL)flag { asynchronous = flag; return self; }
-
- - terminate:sender
- {
- if (running)
- killpg(childPid,SIGKILL);
- return self;
- }
-
- /*
- * effectively sends an EOF to the child process stdin
- */
- - terminateInput
- {
- fclose(fpToChild);
- return self;
- }
-
- @end
-
- @implementation Object(MiscSubprocessDelegate)
-
- - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
- {
- return self;
- }
-
- - subprocess:sender output:(const char *)buffer
- {
- return self;
- }
-
- - subprocess:sender stderrOutput:(const char *)buffer
- {
- return self;
- }
-
- - subprocess:sender error:(const char *)errorString
- {
- perror(errorString);
- return self;
- }
-
- @end
-
-
- @implementation MiscSubprocess(Convenience)
-
- // These were automatically generated using cnvwrap.
-
- - init
- { return [self init:NULL withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:YES]; }
-
- - init:(const char *)aString
- { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:YES]; }
-
- - init:(const char *)aString withDelegate:theDelegate
- { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:NO asynchronously:YES]; }
-
- - init:(const char *)aString keepEnvironment:(BOOL)flag
- { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:NO asynchronously:YES]; }
-
- - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag
- { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:NO asynchronously:YES]; }
-
- - init:(const char *)aString withPtys:(BOOL)ptyFlag
- { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:ptyFlag asynchronously:YES]; }
-
- - init:(const char *)aString withDelegate:theDelegate withPtys:(BOOL)ptyFlag
- { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:ptyFlag asynchronously:YES]; }
-
- - init:(const char *)aString keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
- { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:ptyFlag asynchronously:YES]; }
-
- - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
- { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:ptyFlag asynchronously:YES]; }
-
- - init:(const char *)aString asynchronously:(BOOL)async
- { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:NO asynchronously:async]; }
-
- - init:(const char *)aString withDelegate:theDelegate asynchronously:(BOOL)async
- { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:NO asynchronously:async]; }
-
- - init:(const char *)aString keepEnvironment:(BOOL)flag asynchronously:(BOOL)async
- { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:NO asynchronously:async]; }
-
- - init:(const char *)aString withDelegate:theDelegate keepEnvironment:(BOOL)flag asynchronously:(BOOL)async
- { return [self init:aString withDelegate:theDelegate keepEnvironment:flag withPtys:NO asynchronously:async]; }
-
- - init:(const char *)aString withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async
- { return [self init:aString withDelegate:nil keepEnvironment:YES withPtys:ptyFlag asynchronously:async]; }
-
- - init:(const char *)aString withDelegate:theDelegate withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async
- { return [self init:aString withDelegate:theDelegate keepEnvironment:YES withPtys:ptyFlag asynchronously:async]; }
-
- - init:(const char *)aString keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag asynchronously:(BOOL)async
- { return [self init:aString withDelegate:nil keepEnvironment:flag withPtys:ptyFlag asynchronously:async]; }
-
- - execute:(const char *)aString
- { return [self execute:aString withPtys:usePtys asynchronously:asynchronous]; }
-
- - execute:(const char *)aString withPtys:(BOOL)ptyFlag
- { return [self execute:aString withPtys:ptyFlag asynchronously:asynchronous]; }
-
- - execute:(const char *)aString asynchronously:(BOOL)async
- { return [self execute:aString withPtys:usePtys asynchronously:async]; }
-
- @end